﻿/* *****************************************************************************
ShoutcastRunner 0.3

Copyright (c) KEXP 90.3 FM Seattle. All rights reserved. 
www.kexp.org
author: Louis O'Callaghan (louis@kexp.org)
October 2012

Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0  

THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
MERCHANTABLITY OR NON-INFRINGEMENT. 

See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.

SHOUTcast™ is a trademark of AOL LLC.
***************************************************************************** */

using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using log4net;

namespace Kexp.ShoutcastRunner
{
	public class ShoutcastProcess
	{
		private static readonly ILog Log = LogManager.GetLogger(typeof(ShoutcastProcess));

		private const string LogName = "sc_serv.log";
		private const string W3CLogName = "sc_w3c.log";
		private const string ExeName = "sc_serv.exe";
		private const int StopShoutcastSleepInterval = 3000; // 3 secs

		private readonly DirectoryInfo _shoutcastDirectory;
		private readonly ProcessStartInfo _procInfo;
		private Process _shoutcastProc;
		private FileSystemWatcher _watcher;

		public ShoutcastProcess()
            : this(Settings.ShoutcastDirectory) { }

		public ShoutcastProcess(string shoutcastDirectory)
		{
			_shoutcastDirectory = new DirectoryInfo(shoutcastDirectory);
			if (!_shoutcastDirectory.Exists)
				throw new ShoutcastConfigurationException(string.Format("Shoutcast directory doesn't exist at {0}", shoutcastDirectory));

			var exeInfo = new FileInfo(Path.Combine(shoutcastDirectory, ExeName));
			if (!exeInfo.Exists)
				throw new ShoutcastConfigurationException(string.Format("Shoutcast exe ({0}) doesn't exist.", ExeName));

			// hide the process from the desktop, as recommended in this article:
			// http://stackoverflow.com/questions/1369236/how-to-run-console-application-from-windows-service
			_procInfo = new ProcessStartInfo(exeInfo.FullName)
			{
				UseShellExecute = false,
				RedirectStandardError = false,
				RedirectStandardInput = false,
				RedirectStandardOutput = false,
				CreateNoWindow = true,
				ErrorDialog = false,
				WindowStyle = ProcessWindowStyle.Hidden
			};
		}

		public event FileSystemEventHandler ContentChanged;

		public bool IsRunning { get; private set; }

		public bool MonitorContent
		{
		    get { return _watcher != null && _watcher.EnableRaisingEvents; }
		    set
		    {
				if (value)
				{
                    var shoutcastContentPath = Settings.ShoutcastContentDirectory;
					if (shoutcastContentPath.IsNullOrEmptyOrWhiteSpace() 
						|| !Directory.Exists(shoutcastContentPath))
						throw new Exception("Can't find the shoutcast content directory. Check the config");

					Log.Debug(string.Format("Content directory: {0}", shoutcastContentPath));

					if (_watcher != null)
						_watcher.Dispose();

					_watcher = new FileSystemWatcher
					{
						Path = shoutcastContentPath,
						NotifyFilter = NotifyFilters.LastWrite,
						Filter = "*.mp3",
						IncludeSubdirectories = false,
						InternalBufferSize = 4096
					};

					_watcher.Changed += ContentChanged;
					Log.Info("Starting file system watcher.");
					_watcher.EnableRaisingEvents = true;
				}
				else
				{
					if (_watcher != null)
						_watcher.Dispose();

					_watcher = null;
				}
		    }
		}

		public void Close()
		{
			// todo: consider implementing IDisposable
			if (IsRunning)
				Stop();

			if (_watcher != null)
				_watcher.Dispose();
		}

		public void Start()
		{
			Log.Info("Attempting to start Shoutcast...");

			try
			{
				if (_shoutcastProc == null)
					_shoutcastProc = Process.Start(_procInfo);

				// _watcher will only be null if we never started the monitor or if we explicity stopped it
				// conversely, if it's not null then it means we should be monitoring
				if (_watcher != null)
					_watcher.EnableRaisingEvents = true;

				Log.Info("Started.");
				IsRunning = true;
			}
			catch (Exception e)
			{
				Log.Error("Failed with exception.", e);
			}
		}

		public void Stop()
		{
			if (_shoutcastProc == null)
				return;

			if (MonitorContent)
				_watcher.EnableRaisingEvents = false;

			Log.Info("Stopping Shoutcast...");

			_shoutcastProc.Kill();
			_shoutcastProc.Close();
			_shoutcastProc.Dispose();
			_shoutcastProc = null;

			// todo: consider replacing this with checking for locks on one of the log files
			Thread.Sleep(StopShoutcastSleepInterval);

			Log.Info("Shoutcast stopped.");
			IsRunning = false;
		}

		public void Restart()
		{
			Stop();
			Start();
		}

		public void ParseDailyLogs()
		{
            if (Settings.ParseDailyLogs)
            {
                Log.Info("Attempting to parse daily logs.");
                
                ShoutcastLogParser.ParseDailyLogs(Path.Combine(_shoutcastDirectory.FullName, LogName), Settings.OverwriteLogSizeLimit);

                Log.Info("Done parsing daily logs.");
            }

            if (Settings.ParseW3CDailyLogs != "false")
            {
                Log.Info("Attempting to parse daily W3C logs.");

                if (Settings.ParseW3CDailyLogs == "triton")
                    W3CLogParser.ParseTritonDailyLogs(Path.Combine(_shoutcastDirectory.FullName, W3CLogName), Settings.OverwriteW3CSizeLimit);
                else
                    W3CLogParser.ParseDailyLogs(Path.Combine(_shoutcastDirectory.FullName, W3CLogName), Settings.OverwriteW3CSizeLimit);

                Log.Info("Done parsing daily W3C logs.");
            }
		}

		public void RotateLogs()
		{
			// todo: consider allowing user to specify the output filename pattern
			Log.Info("Attempting to rotate logs.");

			var logInfo = new FileInfo(Path.Combine(_shoutcastDirectory.FullName, LogName));
			var w3CLogInfo = new FileInfo(Path.Combine(_shoutcastDirectory.FullName, W3CLogName));
			if (!logInfo.Exists && !w3CLogInfo.Exists)
				throw new ShoutcastConfigurationException("Can't find any log files to rotate.");

			bool needToRestart = IsRunning && (logInfo.Exists || w3CLogInfo.Exists);
			if (needToRestart)
				Stop();

			if (logInfo.Exists)
				Helpers.RotateLogFile(logInfo, true);
			else
				Log.Error(string.Format("Can't find {0} to rotate.", LogName));

			if (w3CLogInfo.Exists)
				Helpers.RotateLogFile(w3CLogInfo, true);
			else
				Log.Error(string.Format("Can't find {0} to rotate.", W3CLogName));

			if (needToRestart)
				Start();

			Log.Info("Done rotating logs.");
		}
	}

	public class ShoutcastConfigurationException : Exception
	{
		public ShoutcastConfigurationException(string error) : base(error) {}
	}
}
